﻿/*
   Copyright 2011 Softisis

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Reflection;
using FOM.Interfaces;
using FOM.Utils;

namespace FOM.Impl.Converters
{
    /// <summary>
    /// This converter copies all properties of the source object, to the target object
    /// (whathever the types ares).
    /// The matching rule is on the equality of property name.
    /// A parameter can be given to the "new" to tell the converter to copy only value type AND string
    /// (and not the references).
    /// </summary>
    public class PropertyCopierConv : IConverter
    {
        protected bool ValueTypesOnly;

        /// <summary>
        /// Default builder. All properties are copied (value type AND references).
        /// </summary>
        public PropertyCopierConv()
        {
            
        }

        /// <summary>
        /// Instanciate a converter, by telling that only value types are copied (value types AND string).
        /// </summary>
        /// <param name="valueTypesOnly"></param>
        public PropertyCopierConv(bool valueTypesOnly)
        {
            ValueTypesOnly = valueTypesOnly;
        }


        /// <summary>
        /// Gets all the props.
        /// </summary>
        /// <param name="t">the type</param>
        /// <returns>the props</returns>
        protected virtual IEnumerable<PropertyInfo> CollectProperties(Type t)
        {
            // get all the props, even object reference (false)
            return ObjectHelper.CollectProperties(t, ValueTypesOnly);
        }

        /// <summary>
        /// protected method to convert from either the source or the orginal object.
        /// Code is factorised here ;-)
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="fromOriginal"></param>
        /// <returns></returns>
        protected virtual object ConvertProtected(MappingContext ctx, bool fromOriginal)
        {
            var source = fromOriginal ? ctx.ReplacedDstObject : ctx.ObjectSrc;

            if (source == null) // what else ...
                return source;

            var TSource = fromOriginal ? ctx.ReplacedDstObject.GetType() : ctx.Tsrc;

            if (TSource == null)
                return null;

            // value types or string are return itself, as it does not contains properties
            if (TSource.IsValueType || TSource == typeof(string))
                return source;

            // get all the props
            var props = CollectProperties(TSource);

            bool toIgnore = false; // store if keep the prop or not
            foreach (var pi in props)
            {
                if (!pi.CanRead)
                    continue;

         

                toIgnore = false;
                if (ctx.ExcludedNodes != null)
                {
                    foreach (var en in ctx.ExcludedNodes)
                    {
                        // Append the prop name to the source and target paths of objects to convert
                        var v1 = ctx.PathLink.SourcePath == "" ? pi.Name : (ctx.PathLink.SourcePath + "." + pi.Name);
                        var v2 = ctx.PathLink.TargetPath == "" ? pi.Name : (ctx.PathLink.TargetPath + "." + pi.Name);

                        // because we receive full pathes from the engine, we can do a direct match
                        if (v1 == en.SourcePath && v2 == en.TargetPath)
                        {
                            toIgnore = true;
                            break;
                        }
                    }
                }

                if (toIgnore)
                    continue;// Property ignored

                // get the target prop info by its name
                var dstpi = ctx.Tdst.GetProperty(pi.Name);
                if (dstpi != null && dstpi.CanWrite)
                {
                    // must exist with this name, otherwise, ignore it
                    var zeval = pi.GetValue(source, null);
                    dstpi.SetValue(ctx.ObjectDst, zeval, null);
                }
            }
            // we return the source object, altered by set values.
            return ctx.ObjectDst;
        }

        public virtual object Convert(MappingContext ctx)
        {
            return ConvertProtected(ctx, false);
        }

        public IConverter Reverse()
        {
            return this;
        }
    }
}
